package robocup.component.geometry.analysers;

import java.util.LinkedList;

import robocup.component.SConf;
import robocup.component.actions.KickAction;
import robocup.component.geometry.Circle;
import robocup.component.geometry.CircleSegment;
import robocup.component.geometry.LineSegment;
import robocup.component.geometry.StraightLine;
import robocup.component.geometry.Vektor;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.RELATIVE;

/**
 * @author Thomas Karbe
 * 
 * This class represents all possible ball positions a kick with a given
 * acceleration power. It uses CircleSegments to represent the possible
 * positions and makes it possible, to calculate with geometry.
 */
public class PossibleBallsAfterPoweredAcc {

  private final Ball          ball             = Ball.getDummyInstance();
  private final Player        shootingPlayer   = Player.getDummyInstance();
  private ACTION_TYPE         actionType;
  private final CircleSegment shotCirc         = CircleSegment
                                                   .getDummyInstance();
  private final CircleSegment maxCirc          = CircleSegment
                                                   .getDummyInstance();
  private final Vektor        dummyVektor      = Vektor.getDummyInstance();
  private double              kickPow          = 0.;
  private final LineSegment   dummyLineSegment = LineSegment.getDummyInstance();
  /**
   * start of the max circ
   */
  private double              borderAngle1     = 0;
  /**
   * end of the max circ
   */
  private double              borderAngle2     = 0;
  private int                 simulatedCycle   = 1;
  private final Circle        shotCircle       = Circle.getDummyInstance();
  private final Circle        maxCircle        = Circle.getDummyInstance();
  private final Circle        dummyCircle      = Circle.getDummyInstance();
  private double              accLength;
  private double              minAngle;
  private double              maxAngle;

  private enum ACTION_TYPE {
    /**
     * a kick
     */
    KICK_ACTION,
    TACKLE
  }

  /**
   * Creates an uninitialized object.
   */
  public PossibleBallsAfterPoweredAcc() {

    this.calculateValues();
  }

  /**
   * Creates an object based on possible KickActions with power 100.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   */
  public PossibleBallsAfterPoweredAcc(
      final Player p,
      final Ball b) {

    this.ball.copy(b);
    this.shootingPlayer.copy(p);
    if (p.canKick(b)) {
      this.actionType = ACTION_TYPE.KICK_ACTION;
      this.kickPow = 100;
    }
    else {
      this.actionType = null;
    }
    this.calculateValues();
  }

  /**
   * Creates an object based on possible KickActions withe the given angle of
   * the KickAction.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param a
   *          the KickAction that delivers the kickAngle (power stays variable)
   */
  public PossibleBallsAfterPoweredAcc(
      final Player p,
      final Ball b,
      final KickAction a) {

    this.ball.copy(b);
    this.shootingPlayer.copy(p);
    if (p.canKick(b)) {
      this.actionType = ACTION_TYPE.KICK_ACTION;
      this.kickPow = a.getPower();
    }
    else {
      this.actionType = null;
    }
    this.calculateValues();
  }

  /**
   * Creates an object based on possible KickActions withe the given power.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param pow
   *          kickpower (angle stays variable)
   */
  public PossibleBallsAfterPoweredAcc(
      final Player p,
      final Ball b,
      final double pow) {

    this.ball.copy(b);
    this.shootingPlayer.copy(p);
    if (p.canKick(b)) {
      this.actionType = ACTION_TYPE.KICK_ACTION;
      this.kickPow = pow;
    }
    else {
      this.actionType = null;
    }
    this.calculateValues();
  }

  /**
   * Copies the values of acc into this object.
   * 
   * @param acc
   *          the PossibleBalls to copy
   */
  public PossibleBallsAfterPoweredAcc(
      final PossibleBallsAfterPoweredAcc acc) {

    this.actionType = acc.actionType;
    this.ball.copy(acc.ball);
    this.borderAngle1 = acc.borderAngle1;
    this.borderAngle2 = acc.borderAngle2;
    this.shotCirc.copy(acc.shotCirc);
    this.maxCirc.copy(acc.maxCirc);
    this.kickPow = acc.kickPow;
    this.shootingPlayer.copy(acc.shootingPlayer);
    this.simulatedCycle = acc.simulatedCycle;
    this.accLength = acc.accLength;
  }

  /**
   * Creates an object based on possible KickActions with power 100.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   */
  public final void setParams(final Player p,
      final Ball b) {

    this.ball.copy(b);
    this.shootingPlayer.copy(p);
    if (p.canKick(b)) {
      this.actionType = ACTION_TYPE.KICK_ACTION;
      this.kickPow = 100;
    }
    else {
      this.actionType = null;
    }
    this.calculateValues();
    this.simulatedCycle = 1;
  }

  /**
   * Creates an object based on possible KickActions withe the given angle of
   * the KickAction.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param a
   *          the KickAction that delivers the kickPower (angle stays variable)
   */
  public final void setParams(final Player p,
      final Ball b,
      final KickAction a) {

    this.ball.copy(b);
    this.shootingPlayer.copy(p);
    if (p.canKick(b)) {
      this.actionType = ACTION_TYPE.KICK_ACTION;
      this.kickPow = a.getPower();
    }
    else {
      this.actionType = null;
    }
    this.calculateValues();
    this.simulatedCycle = 1;
  }

  /**
   * Creates an object based on possible KickActions withe the given angle.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param pow
   *          the the kickPower (angle stays variable)
   */
  public final void setParams(final Player p,
      final Ball b,
      final double pow) {

    this.ball.copy(b);
    this.shootingPlayer.copy(p);
    if (p.canKick(b)) {
      this.actionType = ACTION_TYPE.KICK_ACTION;
      this.kickPow = pow;
    }
    else {
      this.actionType = null;
    }
    this.calculateValues();
  }

  /**
   * Creates an object based on possible KickActions withe the given angle.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param pow
   *          the the kickPower (angle stays variable)
   */
  public final void setTackleParams(final Player p,
      final Ball b,
      final double pow) {

    this.ball.copy(b);
    this.shootingPlayer.copy(p);
    this.actionType = ACTION_TYPE.TACKLE;
    this.kickPow = pow;
    this.calculateValues();
  }

  private final void calculateValues() {

    if (this.actionType == null) {
      this.shotCirc.reset();
      this.maxCirc.reset();
      this.kickPow = 0.;
      this.borderAngle1 = 0;
      this.borderAngle2 = 0;
      this.minAngle = -180;
      this.maxAngle = 180;
      return;
    }
    if (this.actionType == ACTION_TYPE.KICK_ACTION) {
      final double kpr = SConf.getInstance().KICK_POWER_RATE;
      final double powerReduction = 1 - Math.abs(this.shootingPlayer
          .getAngleRel(this.ball, RELATIVE.TO_BODY) / 720) - 0.25 * ((this.shootingPlayer
          .getDistance(this.ball) - SConf.getInstance().BALL_SIZE - this.shootingPlayer
          .getPConf().PLAYER_SIZE) / this.shootingPlayer.getPConf().KICKABLE_MARGIN);
      this.accLength = this.kickPow * powerReduction * kpr;
      this.shotCircle.setCenter(this.ball.getPosition().add(
          this.ball.getSpeed()));
      this.shotCircle.setRadius(this.accLength);
      this.maxCircle.setCenter(this.ball.getPosition());
      this.maxCircle.setRadius(SConf.getInstance().BALL_SPEED_MAX);
      this.shotCirc.copyCenter(this.shotCircle.getCenter());
      this.shotCirc.setRadius(this.shotCircle.getRadius());
      this.maxCirc.copyCenter(this.maxCircle.getCenter());
      this.maxCirc.setRadius(this.maxCircle.getRadius());
      final LinkedList<Vektor> iPoints = this.shotCircle
          .getIntersectionPoints(this.maxCircle);
      if (iPoints.size() < 2) {
        this.shotCirc.setStartAngle(-180);
        this.shotCirc.setEndAngle(180);
        this.maxCirc.setStartAngle(0);
        this.maxCirc.setEndAngle(0);
        this.borderAngle1 = 0;
        this.borderAngle2 = 0;
      }
      else {
        double angle1 = this.shotCircle.getCenter().getAngleTo(iPoints.get(0));
        double angle2 = this.shotCircle.getCenter().getAngleTo(iPoints.get(1));
        if (Vektor.isAngleBetween(angle1, this.ball.getSpeed().getAngle(),
            angle2)) {
          this.shotCirc.setStartAngle(angle2);
          this.shotCirc.setEndAngle(angle1);
        }
        else {
          this.shotCirc.setStartAngle(angle1);
          this.shotCirc.setEndAngle(angle2);
        }
        angle1 = this.maxCircle.getCenter().getAngleTo(iPoints.get(0));
        angle2 = this.maxCircle.getCenter().getAngleTo(iPoints.get(1));
        if (Vektor.isAngleBetween(angle1, this.ball.getSpeed().getAngle(),
            angle2)) {
          this.maxCirc.setStartAngle(angle1);
          this.maxCirc.setEndAngle(angle2);
        }
        else {
          this.maxCirc.setStartAngle(angle2);
          this.maxCirc.setEndAngle(angle1);
        }
        this.borderAngle1 = this.maxCirc.getStartAngle();
        this.borderAngle2 = this.maxCirc.getEndAngle();
      }
    }
    if (this.actionType == ACTION_TYPE.TACKLE) {
      final double kpr = SConf.getInstance().KICK_POWER_RATE;
      // final double powerReduction = 1;
      // 1 - Math.abs(this.shootingPlayer
      // .getAngleRel(this.ball, RELATIVE.TO_BODY) / 720) - 0.25 *
      // ((this.shootingPlayer
      // .getDistance(this.ball) - SConf.getInstance().BALL_SIZE -
      // this.shootingPlayer
      // .getPConf().PLAYER_SIZE) /
      // this.shootingPlayer.getPConf().KICKABLE_MARGIN);
      this.accLength = this.kickPow * kpr;
      this.shotCircle.setCenter(this.ball.getPosition().add(
          this.ball.getSpeed()));
      this.shotCircle.setRadius(this.accLength);
      this.maxCircle.setCenter(this.ball.getPosition());
      this.maxCircle.setRadius(SConf.getInstance().BALL_SPEED_MAX);
      this.shotCirc.copyCenter(this.shotCircle.getCenter());
      this.shotCirc.setRadius(this.shotCircle.getRadius());
      this.maxCirc.copyCenter(this.maxCircle.getCenter());
      this.maxCirc.setRadius(this.maxCircle.getRadius());
      final LinkedList<Vektor> iPoints = this.shotCircle
          .getIntersectionPoints(this.maxCircle);
      if (iPoints.size() < 2) {
        this.shotCirc.setStartAngle(-180);
        this.shotCirc.setEndAngle(180);
        this.maxCirc.setStartAngle(0);
        this.maxCirc.setEndAngle(0);
        this.borderAngle1 = 0;
        this.borderAngle2 = 0;
      }
      else {
        double angle1 = this.shotCircle.getCenter().getAngleTo(iPoints.get(0));
        double angle2 = this.shotCircle.getCenter().getAngleTo(iPoints.get(1));
        if (Vektor.isAngleBetween(angle1, this.ball.getSpeed().getAngle(),
            angle2)) {
          this.shotCirc.setStartAngle(angle2);
          this.shotCirc.setEndAngle(angle1);
        }
        else {
          this.shotCirc.setStartAngle(angle1);
          this.shotCirc.setEndAngle(angle2);
        }
        angle1 = this.maxCircle.getCenter().getAngleTo(iPoints.get(0));
        angle2 = this.maxCircle.getCenter().getAngleTo(iPoints.get(1));
        if (Vektor.isAngleBetween(angle1, this.ball.getSpeed().getAngle(),
            angle2)) {
          this.maxCirc.setStartAngle(angle1);
          this.maxCirc.setEndAngle(angle2);
        }
        else {
          this.maxCirc.setStartAngle(angle2);
          this.maxCirc.setEndAngle(angle1);
        }
        this.borderAngle1 = this.maxCirc.getStartAngle();
        this.borderAngle2 = this.maxCirc.getEndAngle();
      }
    }
    this.calcMinMaxAngle();
  }

  /**
   * Returns the used Ball
   * 
   * @return the Ball
   */
  public final Ball getBall() {

    return this.ball;
  }

  /**
   * Returns the borderAngle1 (start of maxCirc)
   * 
   * @return borderAngle1
   */
  public final double getBorderAngle1() {

    return this.borderAngle1;
  }

  /**
   * Returns the borderAngle2 (end of maxCirc)
   * 
   * @return borderAngle2
   */
  public final double getBorderAngle2() {

    return this.borderAngle2;
  }

  /**
   * Returns the shotCirc, if the ball is kickable by the shooting player, null
   * otherwise
   * 
   * @return the shotCirc
   */
  public final CircleSegment getShotCirc() {

    if (this.actionType != null) {
      return this.shotCirc;
    }
    return null;
  }

  /**
   * Returns the maxCirc, if the ball is kickable by the shooting player and
   * maxCirc exists, null otherwise
   * 
   * @return the maxCirc
   */
  public final CircleSegment getMaxCirc() {

    if ((this.actionType != null) && !((this.borderAngle1 == 0) && (this.borderAngle2 == 0))) {
      return this.maxCirc;
    }
    return null;
  }

  /**
   * Returns the used kick power
   * 
   * @return the kick power
   */
  public final double getKickPow() {

    return this.kickPow;
  }

  /**
   * Returns the used player
   * 
   * @return the shooting player
   */
  public final Player getShootingPlayer() {

    return this.shootingPlayer;
  }

  /**
   * Returns a new object that is calculated for one cycle more than the actual
   * object.
   * 
   * @return the extrapolated PossibleBalls
   */
  public final PossibleBallsAfterPoweredAcc extrapolate() {

    final PossibleBallsAfterPoweredAcc pb = new PossibleBallsAfterPoweredAcc(
        this);
    pb.extrapolateThis();
    return pb;
  }

  /**
   * Calculates the values for the next cycle.
   */
  public final void extrapolateThis() {

    if (this.getShotCirc() != null) {
      this.shotCirc.setRadius((this.shotCirc.getRadius() / Ball
          .relativeBallMovementAfterNCycles(this.simulatedCycle)) * Ball
          .relativeBallMovementAfterNCycles(this.simulatedCycle + 1));
      this.shotCircle.setRadius(this.shotCirc.getRadius());
      this.dummyVektor.copy(this.shotCirc.getCenter());
      this.dummyVektor.subFromThis(this.ball.getPosition());
      this.dummyVektor.divThis(Ball
          .relativeBallMovementAfterNCycles(this.simulatedCycle));
      this.dummyVektor.multThis(Ball
          .relativeBallMovementAfterNCycles(this.simulatedCycle + 1));
      this.dummyVektor.addToThis(this.ball.getPosition());
      this.shotCirc.copyCenter(this.dummyVektor);
      this.shotCircle.setCenter(this.dummyVektor);
    }
    if (this.getMaxCirc() != null) {
      this.maxCirc.setRadius((this.maxCirc.getRadius() / Ball
          .relativeBallMovementAfterNCycles(this.simulatedCycle)) * Ball
          .relativeBallMovementAfterNCycles(this.simulatedCycle + 1));
      this.maxCircle.setRadius(this.maxCirc.getRadius());
    }
    this.simulatedCycle = this.simulatedCycle + 1;
  }

  /**
   * Returns the cycle for that the values are calculated
   * 
   * @return the actual cycle
   */
  public final int getCycle() {

    return this.simulatedCycle;
  }

  /**
   * Calculates if the ball can be kicked into all directions
   * 
   * @return true if the ball can be kicked into all directions
   */
  public final boolean allAnglesPossible() {

    return this.ball.getSpeed().getLength() < this.accLength;
  }

  /**
   * Calculates the point that the ball arrives when kicked into the direction
   * given by needed angle after the actual number of cycles.
   * 
   * @param neededAngle
   *          the angle in that the ball will move after a kick
   * @param result
   *          here the result is saved. it is set to empty if there is no result
   */
  public final void getPointForShotAngleAbs(final double neededAngle,
      final Vektor result) {

    if ((this.borderAngle1 != this.borderAngle2) && Vektor.isAngleBetween(
        this.borderAngle1, neededAngle, this.borderAngle2)) {
      // on maxcirc
      this.dummyVektor.setVektor(this.maxCirc.getRadius(), neededAngle);
      this.dummyVektor.addToThis(this.ball.getPosition());
      result.copy(this.dummyVektor);
      return;
    }
    // not on maxCirc
    if (this.allAnglesPossible()) {
      // on shotCirc
      this.dummyLineSegment.copyPoint1(this.ball.getPosition());
      this.dummyLineSegment
          .setPolar(2 * this.shotCirc.getRadius(), neededAngle);
      final LinkedList<Vektor> list = this.dummyLineSegment
          .getIntersectionPoints(this.shotCirc);
      if (list.size() != 1) {
        System.err.println("ERROR in kickPoint calculation: list size= " + list
            .size());
        System.err.println("all angles possible");
        System.err.println("angle between borders");
        System.err.println("Line: " + this.dummyLineSegment);
        System.err.println("shotCirc: " + this.shotCircle);
        System.err.println("full object: " + this);
        return;
      }
      result.copy(list.get(0));
      return;
    }
    // not all possible and not on maxCirc
    if (!Vektor.isAngleBetween(this.minAngle + 0.05, neededAngle,
        this.maxAngle - 0.05)) {
      result.setEmpty();
      // TODO
      return;
    }
    // on shotCirc
    this.dummyLineSegment.copyPoint1(this.ball.getPosition());
    this.dummyLineSegment.setPolar(2 * this.shotCirc.getCenter().getDistance(
        this.ball.getPosition()), neededAngle);
    final LinkedList<Vektor> list = this.dummyLineSegment
        .getIntersectionPoints(this.shotCirc);
    if (!((list.size() == 1) || (list.size() == 2))) {
      System.err.println("ERROR in kickPoint calculation: list size= " + list
          .size());
      System.err.println("not all angles possible");
      System.err.println("angle between borders");
      System.err.println("Line: " + this.dummyLineSegment);
      System.err.println("shotCirc: " + this.shotCircle);
      System.err.println("full object: " + this);
      return;
    }
    if (list.size() == 1) {
      result.copy(list.get(0));
      return;
    }
    if (this.ball.getDistance(list.get(0)) > this.ball.getDistance(list.get(1))) {
      result.copy(list.get(0));
      return;
    }
    result.copy(list.get(1));
    return;
  }

  /**
   * Calculates the point that the ball arrives when kicked into the direction
   * given by needed angle after the actual number of cycles.
   * 
   * @param neededAngle
   *          the angle in that the ball will move after a kick
   * @return the point that the ball reaches after the actual number of cycles
   */
  public final Vektor getPointForShotAngleAbs(final double neededAngle) {

    final Vektor v = new Vektor();
    this.getPointForShotAngleAbs(neededAngle, v);
    return v.isEmpty() ? null : v;
  }

  /**
   * Calculates the direction argument that a KickAction needs to kick the Ball
   * into the wished direction.
   * 
   * @param neededAngle
   *          the wished resulting angle of the ball speed
   * @return the needed direction argument for a kick
   */
  public final double getKickDirForResultingAngle(final double neededAngle) {

    if (this.allAnglesPossible()) {
      this.dummyLineSegment.copyPoint1(this.ball.getPosition());
      this.dummyLineSegment
          .setPolar(2 * this.shotCirc.getRadius(), neededAngle);
      final LinkedList<Vektor> list = this.dummyLineSegment
          .getIntersectionPoints(this.shotCircle);
      if (list.size() != 1) {
        System.err.println("ERROR in kickDir calculation");
        System.err.println("all angles possible");
        System.err.println("angle between borders");
        System.err.println("Line: " + this.dummyLineSegment);
        System.err.println("shotCirc: " + this.shotCircle);
        System.err.println("full object: " + this);
      }
      return this.shootingPlayer.getAngleRel(this.shotCirc.getCenter()
          .getAngleTo(list.get(0)), RELATIVE.TO_BODY);
    }
    // not all possible
    if (!Vektor.isAngleBetween(this.minAngle, neededAngle, this.maxAngle)) {
      // not possible
      return Double.MAX_VALUE;
    }
    // shot is possible
    this.dummyLineSegment.copyPoint1(this.ball.getPosition());
    this.dummyLineSegment.setPolar(2 * this.shotCirc.getCenter().getDistance(
        this.ball.getPosition()), neededAngle);
    final LinkedList<Vektor> list = this.dummyLineSegment
        .getIntersectionPoints(this.shotCircle);
    if (!((list.size() == 1) || (list.size() == 2))) {
      System.err.println("ERROR in kickDir calculation");
      System.err.println("all angles possible");
      System.err.println("angle between borders");
      System.err.println("Line: " + this.dummyLineSegment);
      System.err.println("shotCirc: " + this.shotCircle);
      System.err.println("full object: " + this);
    }
    if (list.size() == 1) {
      return this.shootingPlayer.getAngleRel(this.shotCirc.getCenter()
          .getAngleTo(list.get(0)), RELATIVE.TO_BODY);
    }
    if (this.ball.getDistance(list.get(0)) > this.ball.getDistance(list.get(1))) {
      return this.shootingPlayer.getAngleRel(this.shotCirc.getCenter()
          .getAngleTo(list.get(0)), RELATIVE.TO_BODY);
    }
    return this.shootingPlayer.getAngleRel(this.shotCirc.getCenter()
        .getAngleTo(list.get(1)), RELATIVE.TO_BODY);
  }

  /**
   * Calculates the direction argument that a KickAction needs to kick the Ball
   * into the wished direction.
   * 
   * @param neededAngle
   *          the wished resulting angle of the ball speed
   * @return the needed direction argument for a kick
   */
  public final double getTackleDirForResultingAngle(final double neededAngle) {
    return getKickDirForResultingAngle(neededAngle);
  }

  private final void calcMinMaxAngle() {

    if (this.allAnglesPossible()) {
      this.minAngle = -180;
      this.maxAngle = 180;
      return;
    }
    this.dummyVektor.x = this.ball.getPosition().x + this.ball.getSpeed().x / 2;
    this.dummyVektor.y = this.ball.getPosition().y + this.ball.getSpeed().y / 2;
    this.dummyCircle.setCenter(this.dummyVektor);
    this.dummyCircle.setRadius(this.ball.getSpeed().getLength() / 2);
    LinkedList<Vektor> list = this.dummyCircle
        .getIntersectionPoints(this.shotCircle);
    // must be 2 points
    double ang0 = this.ball.getAngleTo(list.get(0));
    double ang1 = this.ball.getAngleTo(list.get(1));
    final Vektor p0 = Vektor.isAngleBetween(ang0, this.ball.getSpeed()
        .getAngle(), ang1) ? list.get(0) : list.get(1);
    final Vektor p1 = Vektor.isAngleBetween(ang0, this.ball.getSpeed()
        .getAngle(), ang1) ? list.get(1) : list.get(0);
    ang0 = this.ball.getAngleTo(p0);
    ang1 = this.ball.getAngleTo(p1);
    boolean p0OK = this.ball.getDistance(p0) < SConf.getInstance().BALL_SPEED_MAX;
    boolean p1OK = this.ball.getDistance(p1) < SConf.getInstance().BALL_SPEED_MAX;
    if (p0OK && p1OK) {
      this.minAngle = ang0;
      this.maxAngle = ang1;
      return;
    }
    list = this.maxCircle.getIntersectionPoints(this.shotCircle);
    // must be 2 points
    double ang2 = this.ball.getAngleTo(list.get(0));
    double ang3 = this.ball.getAngleTo(list.get(1));
    final Vektor p2 = Vektor.isAngleBetween(ang2, this.ball.getSpeed()
        .getAngle(), ang3) ? list.get(0) : list.get(1);
    final Vektor p3 = Vektor.isAngleBetween(ang2, this.ball.getSpeed()
        .getAngle(), ang3) ? list.get(1) : list.get(0);
    ang2 = this.ball.getAngleTo(p2);
    ang3 = this.ball.getAngleTo(p3);
    if (!p0OK) {
      ang0 = ang2;
    }
    if (!p1OK) {
      ang1 = ang3;
    }
    this.minAngle = ang0;
    this.maxAngle = ang1;
  }

  /**
   * Returns the angle at most left of the ball speed that can be reached
   * 
   * @return the min angle
   */
  public final double getMinAngle() {

    return this.minAngle;
  }

  /**
   * Returns the angle at most right of the ball speed that can be reached
   * 
   * @return the min angle
   */
  public final double getMaxAngle() {

    return this.maxAngle;
  }

  /**
   * In some cases there are two points reachable in one shot direction, by
   * using a shot as acceleration or as brake. The braking points are filtered
   * out here.
   * 
   * @param points
   *          the list to be filtered
   */
  public final void filterShortPoints(final LinkedList<Vektor> points) {

    if (this.allAnglesPossible()) {
      return;
    }
    for (int i = points.size() - 1; i >= 0; i--) {
      this.getPointForShotAngleAbs(this.ball.getAngleTo(points.get(i)),
          this.dummyVektor);
      if (this.dummyVektor.isEmpty()) {
        // FIXME: can happen
//        System.err.println("ERROR in filter");
//        System.err.println("angle to " + points.get(i) + ": " + this.ball
//            .getAngleTo(points.get(i)));
//        System.err.println("no needed point exists");
      }
      if (this.dummyVektor.getDistance(points.get(i)) > 0.05) {
        points.remove(i);
      }
    }
  }

  /**
   * Add the intersection points between this object and a LineSegment to the
   * given list.
   * 
   * @param intersect
   *          the LineSegment to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final LineSegment intersect,
      final LinkedList<Vektor> list) {

    this.shotCirc.addIntersectionPointsToList(intersect, list);
    if (this.getMaxCirc() != null) {
      this.maxCirc.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * LineSegment.
   * 
   * @param intersect
   *          the LineSegment to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final LineSegment intersect) {

    final LinkedList<Vektor> res = new LinkedList<Vektor>();
    this.addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a StraightLine to the
   * given list.
   * 
   * @param intersect
   *          the StraightLine to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final StraightLine intersect,
      final LinkedList<Vektor> list) {

    this.shotCirc.addIntersectionPointsToList(intersect, list);
    if (this.getMaxCirc() != null) {
      this.maxCirc.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * StraightLine.
   * 
   * @param intersect
   *          the StraightLine to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final StraightLine intersect) {

    final LinkedList<Vektor> res = new LinkedList<Vektor>();
    this.addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a CircleSegment to the
   * given list.
   * 
   * @param intersect
   *          the CircleSegment to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final CircleSegment intersect,
      final LinkedList<Vektor> list) {

    this.shotCirc.addIntersectionPointsToList(intersect, list);
    if (this.getMaxCirc() != null) {
      this.maxCirc.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * CircleSegment.
   * 
   * @param intersect
   *          the CircleSegment to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final CircleSegment intersect) {

    final LinkedList<Vektor> res = new LinkedList<Vektor>();
    this.addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a Circle to the given
   * list.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final Circle intersect,
      final LinkedList<Vektor> list) {

    this.shotCirc.addIntersectionPointsToList(intersect, list);
    if (this.getMaxCirc() != null) {
      this.maxCirc.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a Circle.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final Circle intersect) {

    final LinkedList<Vektor> res = new LinkedList<Vektor>();
    this.addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a ReachableArea to the
   * given list.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @param back
   *          true if backwards movement is allowed
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final ReachableArea intersect,
      final boolean back,
      final LinkedList<Vektor> list) {

    this.shotCirc.addIntersectionPointsToList(intersect, back, list);
    if (this.getMaxCirc() != null) {
      this.maxCirc.addIntersectionPointsToList(intersect, back, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * ReachableArea.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @param back
   *          true if backwards movement is allowed
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final ReachableArea intersect,
      final boolean back) {

    final LinkedList<Vektor> res = new LinkedList<Vektor>();
    this.addIntersectionPointsToList(intersect, back, res);
    return res;
  }

  /**
   * Returns the highest possible speed that the ball can have when it was
   * kicked to the given direction.
   * 
   * @param startArea
   *          start angle
   * @param endArea
   *          end angle
   * @return the speed
   */
  public final double getHighestSpeedBetweenAngles(final double startArea,
      final double endArea) {

    final double ballang = this.ball.getSpeed().getAngle();
    final double ballSpeed = this.ball.getSpeed().getLength();
    if (Vektor.isAngleBetween(startArea, ballang, endArea)) {
      return Math.min(SConf.getInstance().BALL_SPEED_MAX,
          ballSpeed + this.accLength);
    }
    Vektor v = null;
    if (Vektor.getAngleDistance(endArea, ballang) > Vektor.getAngleDistance(
        ballang, startArea)) {
      v = this.getPointForShotAngleAbs(endArea);
    }
    else {
      v = this.getPointForShotAngleAbs(startArea);
    }
    if (v == null) {
      return 0;
    }
    if (this.simulatedCycle == 1) {
      return v.getDistance(this.ball.getPosition());
    }
    return v.getDistance(this.ball.getPosition()) / Ball
        .relativeBallMovementAfterNCycles(this.simulatedCycle);
  }

  /**
   * (non-Javadoc)
   * 
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {

    final StringBuffer sb = new StringBuffer();
    sb.append("AC_TYPE: ").append(this.actionType).append(" cyc: ").append(
        this.simulatedCycle).append(" minAng: ").append(this.minAngle).append(
        " maxAng: ").append(this.maxAngle).append(" borderAng1: ").append(
        this.borderAngle1).append(" borderAng2: ").append(this.borderAngle2)
        .append(" shotCirc: ").append(this.getShotCirc()).append(" maxCirc: ")
        .append(this.getMaxCirc());
    return sb.toString();
  }
}
